/*
 * The Computer Language Benchmarks Game
 * https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
 *
 * Based on Java version #6 by James McIlree
 * Contributed by Hank Grabowski
 * Adapted for Kotlin/Native by [Your Name]
 */

@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)

import kotlin.native.concurrent.*
import kotlinx.cinterop.*
import platform.posix.*

object Knucleotide {
    private val codes = IntArray(256).apply {
        this['a'.toInt()] = 0; this['A'.toInt()] = 0
        this['c'.toInt()] = 1; this['C'.toInt()] = 1
        this['g'.toInt()] = 2; this['G'.toInt()] = 2
        this['t'.toInt()] = 3; this['T'.toInt()] = 3
    }

    private val nucleotides = charArrayOf('A', 'C', 'G', 'T')

    private fun createFragmentTasks(sequence: ByteArray, fragmentLengths: IntArray): List<WorkerTask> {
        val tasks = mutableListOf<WorkerTask>()
        for (fragmentLength in fragmentLengths) {
            for (index in 0 until fragmentLength) {
                tasks.add(WorkerTask(sequence.copyOf(), index, fragmentLength))
            }
        }
        return tasks
    }

    private class WorkerTask(
        private val sequence: ByteArray,
        private val offset: Int,
        private val fragmentLength: Int
    ) {
        fun execute(): Map<ByteString, ByteString> {
            return createFragmentMap(sequence, offset, fragmentLength)
        }
    }

    private fun createFragmentMap(sequence: ByteArray, offset: Int, fragmentLength: Int): MutableMap<ByteString, ByteString> {
        val map = HashMap<ByteString, ByteString>()
        val lastIndex = sequence.size - fragmentLength + 1
        var key = ByteString(fragmentLength)
        var index = offset
        while (index < lastIndex) {
            key.calculateHash(sequence, index)
            val fragment = map[key]
            if (fragment != null) {
                fragment.count++
            } else {
                map[key] = key
                key = ByteString(fragmentLength)
            }
            index += fragmentLength
        }
        return map
    }

    private fun sumTwoMaps(map1: MutableMap<ByteString, ByteString>, map2: Map<ByteString, ByteString>): Map<ByteString, ByteString> {
        for ((key, value) in map2) {
            val sum = map1[key]
            if (sum != null) {
                sum.count += value.count
            } else {
                map1[key] = value
            }
        }
        return map1
    }

    private fun writeFrequencies(totalCount: Float, frequencies: Map<ByteString, ByteString>): String {
        val list = frequencies.values.sortedByDescending { it.count }
        val sb = StringBuilder()
        for (k in list) {
            sb.append(k.toString())
            sb.append(" ")
            val freq = k.count * 100.0f / totalCount
            sb.append(freq.toString().substring(0, minOf(5, freq.toString().length)))
            sb.append("\n")
        }
        return sb.append("\n").toString()
    }

    private fun writeCount(results: List<Map<ByteString, ByteString>>, nucleotideFragment: String): String {
        val key = ByteString(nucleotideFragment.length)
        key.calculateHash(nucleotideFragment.uppercase().encodeToByteArray(), 0)

        var count = 0
        for (result in results) {
            result[key]?.let { count += it.count }
        }

        return "$count\t${nucleotideFragment.uppercase()}\n"
    }

    fun run(args: Array<String>) {
        val filePath = args.firstOrNull() ?: "dna.txt"
        val sequence = readSequence(filePath)

        val fragmentLengths = intArrayOf(1, 2, 3, 4, 6, 12, 18)
        val tasks = createFragmentTasks(sequence, fragmentLengths)
        val results = executeTasks(tasks)

        val sb = StringBuilder()

        sb.append(writeFrequencies(sequence.size.toFloat(), results[0]))
        sb.append(writeFrequencies((sequence.size - 1).toFloat(), sumTwoMaps(results[1].toMutableMap(), results[2])))

        val nucleotideFragments = arrayOf("ggt", "ggta", "ggtatt", "ggtattttaatt", "ggtattttaatttatagt")
        for (nucleotideFragment in nucleotideFragments) {
            sb.append(writeCount(results, nucleotideFragment))
        }

        print(sb.toString())
    }

    private fun readSequence(filePath: String): ByteArray {
        val buffer = mutableListOf<Byte>()
        var inSequence = false

        fopen(filePath, "r")?.let { file ->
            try {
                memScoped {
                    val readBufferLength = 1024
                    val readBuffer = allocArray<ByteVar>(readBufferLength)

                    while (true) {
                        val line = fgets(readBuffer, readBufferLength, file)?.toKString()
                        if (line == null || line.isEmpty()) break

                        when {
                            line.startsWith(">THREE") -> inSequence = true
                            inSequence && !line.startsWith(">") -> {
                                line.forEach { char ->
                                    val code = codes[char.toInt()]
                                    if (code != 0 || char == 'A' || char == 'a') {
                                        buffer.add(nucleotides[code].toByte())
                                    }
                                }
                            }
                        }
                    }
                }
            } finally {
                fclose(file)
            }
        }

        return buffer.toByteArray()
    }

    private fun executeTasks(tasks: List<WorkerTask>): List<Map<ByteString, ByteString>> {
        val workers = Array(minOf(tasks.size, 4)) { Worker.start() }
        val futures = mutableListOf<Future<Map<ByteString, ByteString>>>()

        try {
            tasks.forEachIndexed { index, task ->
                val worker = workers[index % workers.size]
                futures.add(worker.execute(TransferMode.SAFE, { task }) { it.execute() })
            }

            return futures.map { it.consume { result -> result } }
        } finally {
            workers.forEach { it.requestTermination().result }
        }
    }

    internal class ByteString(size: Int) : Comparable<ByteString> {
        var hash: Int = 0
        var count = 1
        val bytes: ByteArray

        init {
            bytes = ByteArray(size)
        }

        fun calculateHash(k: ByteArray, offset: Int) {
            var temp = 0
            for (i in bytes.indices) {
                val b = k[offset + i]
                bytes[i] = b
                temp = temp * 31 + b.toInt()
            }
            hash = temp
        }

        override fun hashCode(): Int = hash
        override fun equals(other: Any?): Boolean = other is ByteString && bytes.contentEquals(other.bytes)
        override fun compareTo(other: ByteString): Int = other.count - count
        override fun toString(): String = bytes.decodeToString()
    }
}

fun main(args: Array<String>) {
    memScoped {
        Knucleotide.run(args)
    }
}